#include "param.h"
#include "spline.h"
#include "evaluar.h"

void cbool(bool a)
{
	if (a) 
		{cout << "True";}
	else
		{cout << "False";}
	
	return;
}

/* Variables Globales. */
long int cant_puntos 	= 1000;
bool logs 				= true;
bool mostrar_splines 	= false;
bool mostrar_puntos 	= true;
bool mostrar_evaluacion	= false;
bool mostrar_graficos 	= true;
bool resultados			= true;

/* ./main entrada.in salida.out nombre_fig */
int main(int argc, char** argv)
{	
	string nombre_entrada, nombre_fig;	

	if (argc == 2)
	{
		string nombre = argv[1];
		nombre_entrada = nombre + ".in";		
		nombre_fig = nombre;
	}	
	else 
	{
		nombre_entrada = "spline.in";			
		nombre_fig = "spline";		
	}
	
	ifstream entrada(nombre_entrada.c_str());	
	cout.precision(12);
	
	int n, m;
	/* n: # puntos de control 
	 * m: # puntos de muestreo. */
	
	entrada >> n >> m;
	
	if (logs)
		cout << "n : " << n << endl;
		
	/* Hacer que la cantidad de puntos en los que se va a 
	 * dividir la curva dependa de n. */
	cant_puntos = cant_puntos * n;
		
	/* Coordenadas (x,y) de los puntos de control. */
	vPunto coordenadas;	
	forn(i, n)
	{
		double X, Y;
		entrada >> X >> Y;
		coordenadas.push_back(Punto(X, Y));		
	}
	
	if (logs)
	{
		cout << endl << "Coordenadas (x,y) de los puntos" << endl;
		
		forn(i, n-1)
			cout << "(" << coordenadas[i].x << ", " << coordenadas[i].y << "), ";
		cout << "(" << coordenadas[n-1].x << ", " << coordenadas[n-1].y << ")" << endl;
	}
	
	double X, Y;
	entrada >> X >> Y;
	Punto click(X, Y);				
	entrada >> X >> Y;
	Punto nuevo(X, Y);
	
	if (logs)
	{
		cout << endl << "Click: (" << click.x << ", " << click.y << ") \t Nueva posición: (" << nuevo.x << ", " << nuevo.y << ")" << endl;
	}
		
	/* Fin lectura de archivo de entrada. */
	
	vdouble param (n);
	double  var = 1.0;
	//~ param_Uniforme(param, n, var);
	param_Chord(coordenadas, param, n, var);
	//~ param_Centripeta(coordenadas, param, n, var);
	enRango(param, n);
	
	if (logs)
	{
		cout << endl << "Parametrización " << endl;	
		forn(i, n)
			cout << param[i] << " ";	
		cout << endl;
	}
	
	/* Armar nuevos puntos de la forma (s_i, x(s_i)) y (s_i, y(x_i)). */
	vPunto puntos_x (n);
	vPunto puntos_y (n);
	param_puntos(coordenadas, param, puntos_x, puntos_y, n);
	
	if (mostrar_puntos)
	{
		cout << endl << "Puntos en x" << endl;
		forn(i, n-1)
			cout << "(" << puntos_x[i].x << ", " << puntos_x[i].y << "), ";
		cout << "(" << puntos_x[n-1].x << ", " << puntos_x[n-1].y << ")" << endl;
		
		cout << endl << "Puntos en y" << endl;
		forn(i, n-1)
			cout << "(" << puntos_y[i].x << ", " << puntos_y[i].y << "), ";
		cout << "(" << puntos_y[n-1].x << ", " << puntos_y[n-1].y << ")" << endl;
	}
	
	/* Armar los splines. */
	vSpline spline_x (n-1);
	vSpline spline_y (n-1);
	
	/* Cuando n es 2 se resuelve por separado. */
	if (n == 2)
	{
		spline_x[0].a = puntos_x[0].y;
		spline_x[0].b = (puntos_x[1].y - puntos_x[0].y)/(puntos_x[1].x - puntos_x[0].x);
		spline_x[0].c = 0;
		spline_x[0].d = 0;
		spline_x[0].x_i = puntos_x[0].x;
		spline_x[0].min = puntos_x[0].x;
		spline_x[0].max = puntos_x[1].x;
		
		spline_y[0].a = puntos_y[0].y;
		spline_y[0].b = (puntos_y[1].y - puntos_y[0].y)/(puntos_y[1].x - puntos_y[0].x);
		spline_y[0].c = 0;
		spline_y[0].d = 0;
		spline_y[0].x_i = puntos_y[0].x;
		spline_y[0].min = puntos_y[0].x;
		spline_y[0].max = puntos_y[1].x;		
	}
	else
	{
		interpolar(puntos_x, spline_x, n);
		interpolar(puntos_y, spline_y, n);	
	}
	
	if (mostrar_splines)
	{
		cout << endl << "Spline en x" << endl;	
		forn(i, n-1)
		{		
			double punto = spline_x[i].x_i;
			cout << "S_" << i << "(x) = " << spline_x[i].d << "(x-" << punto << ")^3 + ";
			cout << spline_x[i].c << "(x-" << punto << ")^2 + ";
			cout << spline_x[i].b << "(x-" << punto << ") + ";
			cout << spline_x[i].a << "\t";
			cout << " x en [" << spline_x[i].min << ", " << spline_x[i].max << "]" << endl;
		}
		
		cout << endl << "Spline en y" << endl;
		
		forn(i, n-1)
		{		
			double punto = spline_y[i].x_i;
			cout << "S_" << i << "(y) = " << spline_y[i].d << "(y-" << punto << ")^3 + ";
			cout << spline_y[i].c << "(y-" << punto << ")^2 + ";
			cout << spline_y[i].b << "(y-" << punto << ") + ";
			cout << spline_y[i].a << "\t";
			cout << " y en [" << spline_y[i].min << ", " << spline_y[i].max << "]" << endl;
		}		
	}
	
	if (logs)
	{
		bool res;
		res = verificar(puntos_x, spline_x, n);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en x" << endl;	
		res = verificar(puntos_y, spline_y, n);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en y" << endl;
	}
	
	/* Calcular uniformemente los puntos de muestreo. */
	vdouble muestreo (m);
	muestreo_Uniforme(muestreo, m);
	
	if (logs)
	{
		cout << endl << "Puntos de muestreo" << endl;
		forn(i, m)
			cout << muestreo[i] << " ";
		cout << endl;
	}
	
	/* Obtener coordenadas (x,y) de cada punto de muestreo según la spline. */
	vPunto muestreados (m);
	evaluar(muestreo, muestreados, spline_x, spline_y, n, m);
	
	if (mostrar_evaluacion)
	{
		cout << endl << "Muestreados" << endl;
		forn(i, m)	
			cout << muestreados[i].x << " " << muestreados[i].y << endl;
	}
	
	if (mostrar_graficos)
	{
		graficar(cant_puntos, nombre_fig, nombre_fig + " curva inicial.", spline_x, spline_y, n);	
	}	

	/* Calcular el punto perteneciente al spline más próximo al punto 
	 * en el cual el usuario hizo click. */
	Punto proximo;
	double t;
	t = mas_Proximo(cant_puntos, spline_x, spline_y, click, proximo, n);	
	
	if (logs)
	{
		cout << endl << "Punto más próximo" << endl;
		cout << proximo.x << " " << proximo.y << " t: " << t << " Distancia al click: " << fabs(distancia(click, spline_x, spline_y, t, n)) << endl;
	}
	
	/* Nuevos puntos para x y para y. */
	Punto nuevo_x;
	Punto nuevo_y;
	vPunto nuevos_puntos_x;
	vPunto nuevos_puntos_y;
	
	nuevo_x.x = t;
	nuevo_x.y = nuevo.x;
	nuevo_y.x = t;
	nuevo_y.y = nuevo.y;
	
	int pos = ubicacion(nuevo_x, puntos_x);
	agregar_punto(nuevo_x, pos, puntos_x, nuevos_puntos_x);
	agregar_punto(nuevo_y, pos, puntos_y, nuevos_puntos_y);
	
	if (mostrar_puntos)
	{
		cout << endl << "Nuevos puntos en x" << endl;
		forn(i, n)
			cout << "(" << nuevos_puntos_x[i].x << ", " << nuevos_puntos_x[i].y << "), ";
		cout << "(" << nuevos_puntos_x[n].x << ", " << nuevos_puntos_x[n].y << ")" << endl;
		
		cout << endl << "Nuevos puntos en y" << endl;
		forn(i, n)
			cout << "(" << nuevos_puntos_y[i].x << ", " << nuevos_puntos_y[i].y << "), ";
		cout << "(" << nuevos_puntos_y[n].x << ", " << nuevos_puntos_y[n].y << ")" << endl;
	}
		
	/* Agregar una posición a los splines. */	
	spline_x.push_back(Spline());
	spline_y.push_back(Spline());
	
	/* Interpolar nuevamente usando los nuevos puntos. */
	interpolar(nuevos_puntos_x, spline_x, n+1);
	interpolar(nuevos_puntos_y, spline_y, n+1);	
	
	if (mostrar_splines)
	{
		cout << endl << "Spline en x" << endl;	
		forn(i, n)
		{		
			double punto = spline_x[i].x_i;
			cout << "S_" << i << "(x) = " << spline_x[i].d << "(x-" << punto << ")^3 + ";
			cout << spline_x[i].c << "(x-" << punto << ")^2 + ";
			cout << spline_x[i].b << "(x-" << punto << ") + ";
			cout << spline_x[i].a << "\t";
			cout << " x en [" << spline_x[i].min << ", " << spline_x[i].max << "]" << endl;
		}
		
		cout << endl << "Spline en y" << endl;		
		forn(i, n)
		{		
			double punto = spline_y[i].x_i;
			cout << "S_" << i << "(y) = " << spline_y[i].d << "(y-" << punto << ")^3 + ";
			cout << spline_y[i].c << "(y-" << punto << ")^2 + ";
			cout << spline_y[i].b << "(y-" << punto << ") + ";
			cout << spline_y[i].a << "\t";
			cout << " y en [" << spline_y[i].min << ", " << spline_y[i].max << "]" << endl;
		}
	}
	
	if (logs)
	{
		bool res;
		res = verificar(nuevos_puntos_x, spline_x, n+1);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en x" << endl;	
		res = verificar(nuevos_puntos_y, spline_y, n+1);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en y" << endl;
	}
			
	if (mostrar_graficos)
	{		
		graficar(cant_puntos, nombre_fig + "_agregar_punto", nombre_fig + " moviendo el punto.", spline_x, spline_y, n+1);
	}
			
	vPunto deformados (m);
	evaluar(muestreo, deformados, spline_x, spline_y, n+1, m);	
	
	if (mostrar_evaluacion)
	{
		cout << endl << "Deformados" << endl;
		forn(i, m)	
			cout << deformados[i].x << " " << deformados[i].y << endl;
	}

	
	/* Para contestar la pregunta 2.
	 * Agregar el punto nuevo como coordenada (x,y) y obtener una nueva 
	 * parametrización para ver si cambia la curva. 
	 * Como la posición en la que se tiene que ubicar es siempre la 
	 * misma y ya está obtenida, se usa. */
	vPunto nuevas_coordenadas;
	agregar_punto(nuevo, pos, coordenadas, nuevas_coordenadas);
	vdouble nueva_param (n+1);
	param_Chord(nuevas_coordenadas, nueva_param, n+1, var);
	enRango(nueva_param, n+1);
	param_puntos(nuevas_coordenadas, nueva_param, nuevos_puntos_x, nuevos_puntos_y, n+1);
	vSpline nuevo_spline_x (n);
	vSpline nuevo_spline_y (n);
	interpolar(nuevos_puntos_x, nuevo_spline_x, n+1);
	interpolar(nuevos_puntos_y, nuevo_spline_y, n+1);
	
	if (mostrar_puntos)
	{
		cout << endl << "Nuevos puntos en x" << endl;
		forn(i, n)
			cout << "(" << nuevos_puntos_x[i].x << ", " << nuevos_puntos_x[i].y << "), ";
		cout << "(" << nuevos_puntos_x[n].x << ", " << nuevos_puntos_x[n].y << ")" << endl;
		
		cout << endl << "Nuevos puntos en y" << endl;
		forn(i, n)
			cout << "(" << nuevos_puntos_y[i].x << ", " << nuevos_puntos_y[i].y << "), ";
		cout << "(" << nuevos_puntos_y[n].x << ", " << nuevos_puntos_y[n].y << ")" << endl;
	}
	
	if (mostrar_splines)
	{
		cout << endl << "Spline en x" << endl;	
		forn(i, n-1)
		{		
			double punto = nuevo_spline_x[i].x_i;
			cout << "S_" << i << "(x) = " << nuevo_spline_x[i].d << "(x-" << punto << ")^3 + ";
			cout << nuevo_spline_x[i].c << "(x-" << punto << ")^2 + ";
			cout << nuevo_spline_x[i].b << "(x-" << punto << ") + ";
			cout << nuevo_spline_x[i].a << "\t";
			cout << " x en [" << nuevo_spline_x[i].min << ", " << nuevo_spline_x[i].max << "]" << endl;
		}
		
		cout << endl << "Spline en y" << endl;		
		forn(i, n-1)
		{		
			double punto = nuevo_spline_y[i].x_i;
			cout << "S_" << i << "(y) = " << nuevo_spline_y[i].d << "(y-" << punto << ")^3 + ";
			cout << nuevo_spline_y[i].c << "(y-" << punto << ")^2 + ";
			cout << nuevo_spline_y[i].b << "(y-" << punto << ") + ";
			cout << nuevo_spline_y[i].a << "\t";
			cout << " y en [" << nuevo_spline_y[i].min << ", " << nuevo_spline_y[i].max << "]" << endl;
		}
	}
	
	if (mostrar_graficos)
	{
		graficar(cant_puntos, nombre_fig + "_nueva_param", nombre_fig + " parametrizando nuevamente.", nuevo_spline_x, nuevo_spline_y, n+1);	
	}
	
	if (logs)
	{
		bool res;
		res = verificar(nuevos_puntos_x, nuevo_spline_x, n+1);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en x" << endl;	
		res = verificar(nuevos_puntos_y, nuevo_spline_y, n+1);
		if (!res)
			cerr << "Hubo un problema al calcular el spline en y" << endl;
	}
	
	/* Evaluar en varios puntos para ver cómo cambia la curva, y cuánto. */
	vdouble puntos_evaluacion (cant_puntos);
	muestreo_Uniforme(puntos_evaluacion, cant_puntos);
	vPunto deformados_orig (cant_puntos);
	vPunto deformados_nuevos (cant_puntos);
	evaluar(puntos_evaluacion, deformados_orig, spline_x, spline_y, n+1, cant_puntos);
	evaluar(puntos_evaluacion, deformados_nuevos, nuevo_spline_x, nuevo_spline_y, n+1, cant_puntos);	
	
	bool es_igual = true;
	double variacion_max = 0;
	double variacion = 0;
	if (resultados)
	{
		forn(i, cant_puntos)
		{
			if (!igualdad_valida(deformados_orig[i].x, deformados_nuevos[i].x))
			{
				variacion = fabs(deformados_orig[i].x - deformados_nuevos[i].x);
				
				if (variacion > variacion_max)
					variacion_max = variacion;
					
				es_igual = false;
			}
			
			if (!igualdad_valida(deformados_orig[i].y, deformados_nuevos[i].y))
			{
				variacion = fabs(deformados_orig[i].y - deformados_nuevos[i].y);
				
				if (variacion > variacion_max)
					variacion_max = variacion;
					
				es_igual = false;
			}
		}
		
		if (!es_igual)
		{
			cout << endl << "La forma de la curva no es la misma al cambiar la parametrización cuando se agrega el nuevo punto." << endl;
			cout << "La máxima variación encontrada en los puntos evaluados fue: " << variacion_max << endl;
		}
		else
			cout << endl << "Los valores de la curva son los mismo en los puntos evaluados, al cambiar la parametrización cuando se agrega el nuevo punto." << endl;
	}
		
	return 0;
}
